%% ---
%%  Excerpted from "Programming Erlang",
%%  published by The Pragmatic Bookshelf.
%%  Copyrights apply to this code. It may not be used to create training material, 
%%  courses, books, articles, and the like. Contact us if you are in doubt.
%%  We make no guarantees that this code is fit for any purpose. 
%%  Visit http://www.pragmaticprogrammer.com/titles/jaerlang for more book information.
%%---
-module(chat_client).

-import(io_widget, 
	[get_state/1, insert_str/2, set_prompt/2, set_state/2, 
	 set_title/2, set_handler/2, update_state/3, update_buddies/2]).

-export([start/0, test/0, connect/5]).


start() -> 
    connect("localhost", 2223, "AsDT67aQ", "general", "joe").


test() ->
	connect("localhost", 2223, "AsDT67aQ", "general", "joe"),
    connect("localhost", 2223, "AsDT67aQ", "general", "jane"),
    connect("localhost", 2223, "AsDT67aQ", "general", "jim"),
    connect("localhost", 2223, "AsDT67aQ", "general2", "sue").
	   

connect(Host, Port, HostPsw, Group, Nick) ->
    spawn(fun() -> handler(Host, Port, HostPsw, Group, Nick) end).
				 
handler(Host, Port, HostPsw, Group, Nick) ->
    process_flag(trap_exit, true),
    Widget = io_widget:start(self()),
    set_title(Widget, Nick),
    set_state(Widget, Nick),
    set_prompt(Widget, [Nick, " > "]),
    set_handler(Widget, fun parse_command/1),
    start_connector(Host, Port, HostPsw),    
    disconnected(Widget, Group, Nick).

disconnected(Widget, Group, Nick) ->
    receive
	{connected, MM} ->
	    insert_str(Widget, "connected to server\nsending data\n"),
	    lib_chan_mm:send(MM, {login, Group, Nick}),
	    wait_login_response(Widget, MM);
	{Widget, destroyed} ->
	    exit(died);
	{status, S} ->
	    insert_str(Widget, to_str(S)),
	    disconnected(Widget, Group, Nick);
	Other ->
	    io:format("chat_client disconnected unexpected:~p~n",[Other]),
	    disconnected(Widget, Group, Nick)
    end.



wait_login_response(Widget, MM) ->
    receive
	%% ex 5
	{chan, MM, {create_group, Group, Nick}} ->
	    io:format("Usuario ~p (~p) criando grupo ~p~n", [Nick, MM, Group]),
        {Address, Port} = read_address_and_port(Nick),
		Parent = self(),
	    spawn_link(fun() -> chat_group:start_group(MM, Parent, Group, Nick, Address, Port) end),
	    ClientToGroupMM = connect_in_group(Group, Nick, Address, Port),
	    insert_str(Widget, [Nick,"@",pid_to_list(self()),"I'm starting the group\n"]),
    	% Agora esperamos a conexao do MM entre cliente e grupo, e nao mais entre cliente e servidor.
    	wait_login_response(Widget, ClientToGroupMM);
	{chan, MM, {connect_group, Group, Nick, Address, Port}} ->
	    ClientToGroupMM = connect_in_group(Group, Nick, Address, Port),
		% Fecha a conexao que foi aberta com o servidor, jah que estamos conectados direto com o grupo.
		MM ! close, 
	    % Agora esperamos a conexao do MM entre cliente e grupo, e nao mais entre cliente e servidor.
    	wait_login_response(Widget, ClientToGroupMM);
	%% ex 5
	{chan, MM, ack} ->
	    active(Widget, MM);
	Other ->
	    io:format("chat_client login unexpected:~p~n",[Other]),
	    wait_login_response(Widget, MM)
    end.

connect_in_group(Group, Nick, Address, Port) ->
    io:format("Usuario ~p conectando no grupo ~p (~p:~p) ~n", [Nick, Group, Address, Port]),
	ClientToGroupMM = try_to_connect_in_group(Address, Port),
	lib_chan_mm:send(ClientToGroupMM, {login, Nick}),
	ClientToGroupMM.

read_address_and_port(Nick) ->
    {ok, [{port, Port}, _]} = file:consult(Nick++".conf"),
    {localhost, Port}.

active(Widget, MM) ->
     receive
	 {Widget, Nick, Str} ->
	     lib_chan_mm:send(MM, {relay, Nick, Str}),
	     active(Widget, MM);
	 {chan, MM, group_died} ->
		insert_str(Widget, ["The group has died! :-(\n"]),
		active(Widget, MM);
	 {chan, MM, {msg, From, Pid, Str}} ->
	     insert_str(Widget, [From,"@",pid_to_list(Pid)," ", Str, "\n"]),
	     active(Widget, MM);
	 {'EXIT',Widget,windowDestroyed} ->
	     lib_chan_mm:close(MM);
	 {close, MM} ->
	     exit(serverDied);
	 %% Ex 1
	 {chan, MM, {buddies, Buddies}} ->
	     update_buddies(Widget, Buddies),
	     active(Widget, MM);
	 %% Ex 1
	 %% Ex 2
	 {Widget, {get_nicks_in_group, Group}} ->
	     {nicks_in_group, Nicks} = lib_chan:rpc(MM, {get_nicks_in_group, Group}),
	     case Nicks of
	         group_does_not_exist ->
	             insert_str(Widget, ["Group ",Group," does not exist\n"]);
	         Nicks ->
	             insert_str(Widget, ["Members in ",Group,": ",nicks_to_string(Nicks),"\n"])
	     end,
	     active(Widget, MM);
	 %% Ex 2
	 %% Ex 3
	 {Widget, get_all_groups} ->
	     {all_groups, AllGroups} = lib_chan:rpc(MM, get_all_groups),
	     insert_str(Widget, ["Groups: ", groups_to_string(AllGroups), "\n"]),
	     active(Widget, MM);
	 %% Ex 3
	 %% Ex 4
	 {Widget, {send_pvt, Nick, To, Str}} ->
         %insert_str(Widget, [Nick,"@",pid_to_list(MM)," [PVT]", Str, "\n"]),
	     lib_chan_mm:send(MM, {send_pvt, Nick, To, Str}),
	     active(Widget, MM);
	 {chan, MM, {pvt_msg, From, Pid, Str}} ->
	     insert_str(Widget, [From,"@",pid_to_list(Pid)," [PVT]", Str, "\n"]),
	     active(Widget, MM);
	 %% Ex 4
	 {chan_closed, _} ->
	     insert_str(Widget, ["Group has been closed. It is not possible to send messages!\n"]),
	     active(Widget, MM);	 
	 Other ->
	     io:format("chat_client active unexpected:~p~n",[Other]),
	     active(Widget, MM)
     end. 

%% Ex 2
%% A 2a clausula existe apenas para evitar que se coloque a virgula no final da linha
nicks_to_string([]) -> [];
nicks_to_string([{_,Nick}]) -> [Nick];
nicks_to_string([{_,Nick}|T]) -> [Nick,", "|nicks_to_string(T)].
%% Ex 2

%% Ex 3
%% A 2a clausula existe apenas para evitar que se coloque a virgula no final da linha
groups_to_string([]) -> [];
groups_to_string([{Group,_,_,_}]) -> [Group];
groups_to_string([{Group,_,_,_}|T]) -> [Group, ", "|groups_to_string(T)].
%% Ex 3

start_connector(Host, Port, Pwd) ->
    S = self(),
    spawn_link(fun() -> try_to_connect(S, Host, Port, Pwd) end).
    
try_to_connect(Parent, Host, Port, Pwd) ->
    %% Parent is the Pid of the process that spawned this process
    case lib_chan:connect(Host, Port, chat, Pwd, []) of
	{error, _Why} ->
	    Parent ! {status, {cannot, connect, Host, Port}},
	    sleep(2000),
	    try_to_connect(Parent, Host, Port, Pwd);
	{ok, MM} ->
	    lib_chan_mm:controller(MM, Parent),
	    Parent ! {connected, MM},
	    exit(connectorFinished)
    end.

try_to_connect_in_group(Address, Port) ->
	case lib_chan:connect(Address, Port, chat_group, "AsDT67aQ", []) of
		{error, _Why} ->
			sleep(2000),
			try_to_connect_in_group(Address, Port);
		{ok,MM} -> MM
	end.

sleep(T) ->
    receive
    after T -> true
    end.
	    
to_str(Term) ->
    io_lib:format("~p~n",[Term]).

parse_command(Str) -> skip_to_gt(Str).

skip_to_gt(">" ++ T) -> T;
skip_to_gt([_|T])    -> skip_to_gt(T);
skip_to_gt([])       -> exit("no >").
